package org.budo.support.spring.aop.util;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.budo.support.slf4j.Slf4j;
import org.budo.support.spring.context.aware.BudoApplicationContextAware;
import org.budo.support.spring.util.SpringUtil;
import org.slf4j.Logger;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.cache.annotation.CacheAnnotationParser;
import org.springframework.context.ApplicationContext;

/**
 * @author lmw
 * @see org.springframework.aop.framework.AopContext#currentProxy()
 */
public class AopUtil {
    private static final Logger log = Slf4j.getLogger();

    private static final Map<Object, Object> _PROXY_CACHE = new ConcurrentHashMap<Object, Object>();

    private static final Map<Object, Object> _PROXY_FACTORY_CACHE = new ConcurrentHashMap<Object, Object>();

    public static <T> T proxyFactory(T target) {
        if (null == target) {
            return null;
        }

        Object targetProxy = _PROXY_FACTORY_CACHE.get(target); // 缓存
        if (null != targetProxy) {
            return (T) targetProxy;
        }

        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setProxyTargetClass(true);
        proxyFactory.setExposeProxy(true);
        proxyFactory.setTarget(target);
        proxyFactory.addAdvice(new DelegatingIntroductionInterceptor(target));
        targetProxy = proxyFactory.getProxy();

        _PROXY_FACTORY_CACHE.put(target, targetProxy);
        return (T) targetProxy;
    }

    /**
     * !!!注意,如果ioc中有同一个类型的多个Bean,可能导致错误!!!，可改用 AopContext.currentProxy()
     * 传入一个对象，返回与之对应的经过Aop包装的对象
     */
    public static <T> T proxy(T target) {
        if (null == target) {
            return null;
        }

        Object proxy = _PROXY_CACHE.get(target); // 缓存
        if (null != proxy) {
            return (T) proxy;
        }

        if (AopUtils.isAopProxy(target)) {
            _PROXY_CACHE.put(target, target); // 自己
            return target;
        }

        BudoApplicationContextAware applicationContextAware = BudoApplicationContextAware.getInstance();
        if (null == applicationContextAware) {
            log.warn("#42 no applicationContextAware, return it-self : " + target);
            return target; //
        }

        ApplicationContext applicationContext = applicationContextAware.getApplicationContext();
        Class<?> targetType = target.getClass();

        T proxyedBean = (T) SpringUtil.getBean(applicationContext, targetType);
        if (null == proxyedBean) {
            log.debug("#45 not found bean for targetType={}, return it-self: {}", targetType, target);
            return target; //
        }

        _PROXY_CACHE.put(target, proxyedBean); // Bean
        return proxyedBean;
    }

    public static Boolean aopShouldSkip(Method method) {
        Class<?> type = method.getDeclaringClass();
        if (FactoryBean.class.isAssignableFrom(type)) { // 特殊处理
            log.debug("#32 skip " + method);
            return true;
        }

        if (method.getDeclaringClass().equals(Object.class)) {
            return true;
        }

        if (CacheAnnotationParser.class.isAssignableFrom(type)) {
            log.debug("#34 skip " + method);
            return true;
        }

        if (method.getDeclaringClass().getName().startsWith("org.springframework.web.servlet.HandlerAdapter")) {
            return true;
        }

        return false;
    }
}